/**
 * Copyright 2019 吉鼎科技.
 *
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package cn.easyplatform.engine;

import cn.easyplatform.EngineService;
import cn.easyplatform.ServiceException;
import cn.easyplatform.cfg.EngineConfiguration;
import cn.easyplatform.cfg.TaskExecuter;
import cn.easyplatform.cfg.TaskExecuterFactory;
import cn.easyplatform.contexts.ApplicationContext;
import cn.easyplatform.dao.EntityDao;
import cn.easyplatform.engine.service.*;
import cn.easyplatform.entities.EntityInfo;
import cn.easyplatform.entities.beans.project.ProjectBean;
import cn.easyplatform.entities.transform.TransformerFactory;
import cn.easyplatform.i18n.I18N;
import cn.easyplatform.interceptor.CommandContext;
import cn.easyplatform.lang.*;
import cn.easyplatform.services.AbstractService;
import cn.easyplatform.services.SystemServiceId;
import cn.easyplatform.services.project.ProjectService;
import cn.easyplatform.services.system.EntitiesDataSourceSevice;
import cn.easyplatform.services.system.SchedulerService;
import cn.easyplatform.spi.annotation.Plugin;
import cn.easyplatform.spi.annotation.Service;
import cn.easyplatform.spi.annotation.ServiceType;
import cn.easyplatform.spi.engine.Engine;
import cn.easyplatform.spi.engine.EngineFactory;
import cn.easyplatform.spi.extension.ApplicationService;
import cn.easyplatform.spi.service.*;
import cn.easyplatform.utils.resource.GResource;
import cn.easyplatform.utils.resource.Scans;
import org.apache.commons.io.FilenameUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.cache.CacheManager;
import org.apache.shiro.mgt.DefaultSecurityManager;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.Properties;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
 * @author <a href="mailto:davidchen@epclouds.com">littleDog</a> <br/>
 * @since 2.0.0 <br/>
 */
class EngineConfigurationImpl extends EngineConfiguration {

    private TaskExecuterFactory taskExecuterFactory;

    /**
     * @return
     */
    Engine buildEngine() {
        return new EngineImpl(this);
    }

    @Override
    public void start() {
        List<GResource> jars = loadThirdJar();
        initSystemService();
        initPlugins(jars);
        taskExecuterFactory = new EngineTaskExecuterFactory();
    }

    /**
     *
     */
    protected void initEngineService() {
        EngineService es = new IdentityServiceImpl();
        es.setCommandExecutor(commandExecutor);
        engineServices.put(IdentityService.class, es);
        es = new TaskServiceImpl();
        es.setCommandExecutor(commandExecutor);
        engineServices.put(TaskService.class, es);
        es = new ComponentServiceImpl();
        es.setCommandExecutor(commandExecutor);
        engineServices.put(ComponentService.class, es);
        es = new ListServiceImpl();
        es.setCommandExecutor(commandExecutor);
        engineServices.put(ListService.class, es);
        es = new ApplicationServiceImpl();
        es.setCommandExecutor(commandExecutor);
        engineServices.put(cn.easyplatform.spi.service.ApplicationService.class, es);
        es = new AdminServiceImpl();
        es.setCommandExecutor(commandExecutor);
        engineServices.put(AdminService.class, es);
        es = new AddonServiceImpl();
        es.setCommandExecutor(commandExecutor);
        engineServices.put(AddonService.class, es);
        es = new VfsServiceImpl();
        es.setCommandExecutor(commandExecutor);
        engineServices.put(VfsService.class, es);
        es = new ApiServiceImpl();
        es.setCommandExecutor(commandExecutor);
        engineServices.put(ApiService.class, es);
    }

    /**
     * 注册系统服务
     */
    private void initSystemService() {
        // 参数连接池
        if (!services.containsKey(SystemServiceId.SYS_ENTITY_DS)) {
            EntitiesDataSourceSevice ds = new EntitiesDataSourceSevice();
            ds.setEngineConfiguration(this);
            ds.start();
            services.put(ds.getId(), ds);
        }
        // 缓存
        CacheManager cacheManager = ((DefaultSecurityManager) SecurityUtils
                .getSecurityManager()).getCacheManager();
        ApplicationService s = (ApplicationService) cacheManager;
        services.put(s.getId(), s);
        // 作业调度
        SchedulerService ss = new SchedulerService();
        ss.setEngineConfiguration(this);
        services.put(ss.getId(), ss);
        //启动项目
        EntityDao dao = getEntityDao();
        List<EntityInfo> models = dao.getModels();
        for (final EntityInfo entity : models) {
            ProjectBean project = TransformerFactory.newInstance()
                    .transformFromXml(entity);
            AbstractService as = new ProjectService(project);
            as.setEngineConfiguration(this);
            try {
                as.start();
            } catch (Exception e) {
                if (models.size() == 1)//仅单项目
                    throw e;
                if (log.isWarnEnabled())
                    log.warn(String.format("Failed to start project {} {}", project.getId(), project.getName()), e);
            }
        }
        // 启动作业调度
        ss.start();
    }

    /**
     * 加载第3方jar
     */
    private List<GResource> loadThirdJar() {
        String base = getHome() + File.separatorChar + "lib" + File.separatorChar + "third";
        List<GResource> resources = Scans.me().scan(base, "^.+.jar$");
        if (!resources.isEmpty()) {
            Method method = null;
            boolean accessible = false;
            try {
                method = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
                accessible = method.isAccessible();     // 获取方法的访问权限
                if (!accessible)
                    method.setAccessible(true);     // 设置方法的访问权限
                // 获取系统类加载器
                URLClassLoader classLoader = (URLClassLoader) ClassLoader.getSystemClassLoader();
                for (GResource r : resources) {
                    File file = new File(base + File.separatorChar + r.getName());
                    URL url = file.toURI().toURL();
                    try {
                        method.invoke(classLoader, url);
                        if (log.isDebugEnabled())
                            log.debug("load [name={}]", file.getName());
                    } catch (Exception e) {
                        if (log.isErrorEnabled())
                            log.error("load [name={}]失败", file.getName());
                    }
                }
            } catch (Exception e) {
                throw new ServiceException(e.getMessage());
            } finally {
                if (method != null)
                    method.setAccessible(accessible);
            }
        }
        return resources;
    }

    /**
     * 加载插件服务
     */
    private void initPlugins(List<GResource> third) throws ServiceException {
        String home = getHome();
        if (standalone) {
            String base = home + File.separatorChar + "classes";
            List<GResource> resources = Scans.me().scan(base,
                    "^.+(.class|.properties)$");
            setup(base, resources);
            resources = null;
        } else {
            String[] clspaths = System.getProperty("java.class.path").split(
                    "\\;");
            for (String path : clspaths) {
                if (path.endsWith("classes")
                        && path.indexOf("easyplatform-report") < 0
                        && path.indexOf("easyplatform-script") < 0) {
                    List<GResource> resources = Scans.me().scan(path,
                            "^.+(.class|.properties)$");
                    setup(path, resources);
                    resources = null;
                }
            }
        }
        // jar必须是easyplatform开头，例如easyplatform-messages.jar
        String base = home + File.separatorChar + "lib";
        List<GResource> resources = Scans.me().scan(base,
                "^easyplatform(.+)[.]jar$");
        setup(base + File.separatorChar, resources);
        //加载第3方库文件
        setup(base + File.separatorChar + "third" + File.separatorChar, third);
    }

    /**
     * @param resources
     */
    private void setup(String base, List<GResource> resources) {
        List<Resource> i18nFiles = new ArrayList<Resource>();
        List<String> classFiles = new ArrayList<String>();
        for (GResource r : resources) {
            if (r.getName().endsWith(".class")) {// class
                classFiles.add(r.getName());
            } else if (r.getName().endsWith(".jar")) {// jar
                JarFile jf = null;
                try {
                    jf = new JarFile(base + r.getName());
                    for (Enumeration<JarEntry> e = jf.entries(); e
                            .hasMoreElements(); ) {
                        JarEntry entry = e.nextElement();
                        String name = entry.getName();
                        if (name.endsWith(".class"))
                            classFiles.add(name);
                        else if (name.endsWith(".properties")) {
                            String fn = FilenameUtils.getBaseName(name);
                            if (fn.startsWith("easyplatform")) {
                                try {
                                    I18N.loadResource(fn,
                                            jf.getInputStream(entry));
                                } catch (Exception ex) {
                                    throw new ServiceException(
                                            "can't load resource "
                                                    + entry.getName(), ex);
                                }
                            }
                        }
                    }
                } catch (IOException ex) {
                    throw new ServiceException("can't load jar file "
                            + r.getName(), ex);
                } finally {
                    if (jf != null) {
                        try {
                            jf.close();
                        } catch (Throwable e) {
                        }
                        jf = null;
                    }
                }
            } else if (r.getName().endsWith(".properties")) {// 资源文件
                String fn = FilenameUtils.getBaseName(r.getName());
                if (fn.startsWith("easyplatform"))
                    i18nFiles.add(new Resource(r.getName(), fn, r
                            .getInputStream()));
            }
        }
        // 先设置资源文件
        for (Resource r : i18nFiles) {
            if (log.isInfoEnabled())
                log.info("load resource-{}", r.fullname);
            try {
                I18N.loadResource(r.name, r.is);
            } catch (Exception e) {
                throw new ServiceException("can't load resource " + r.fullname,
                        e);
            }
        }
        i18nFiles = null;
        // 然后再启动其它服务
        for (String clazz : classFiles) {
            clazz = clazz.substring(0, clazz.length() - 6).replaceAll(
                    "[/\\\\]", ".");
            if (clazz.indexOf("$") < 0) {
                try {
                    Class<?> cls = Lang.loadClass(clazz);
                    registryPlugin(cls);
                } catch (Throwable e) {
//                    if (e instanceof NoClassDefFoundError)
//                        throw new ServiceException("No found class :" + e.getMessage());
                    if (e instanceof ServiceException)
                        throw (ServiceException) e;
//                    if (log.isWarnEnabled())
//                        log.error("Resource can't map to Class, Resource {}", clazz);
                }
            }
        }
        classFiles = null;
    }

    /**
     * @param clazz
     */
    private void registryPlugin(Class<?> clazz) {
        Plugin plugin = clazz.getAnnotation(Plugin.class);
        if (plugin != null) {// 存在外部插件
            Properties props = new Properties();
            if (!Strings.isBlank(plugin.config())) {// 加载配置文件
                StringBuilder sb = new StringBuilder(EngineFactory.me().getConfig("easyplatform.conf"));
                sb.append(File.separatorChar).append(plugin.config());
                if (log.isDebugEnabled())
                    log.debug(I18N.getLabel("easyplatform.plugins.config", sb));
                File file = new File(sb.toString());
                InputStream is = null;
                try {
                    if (file.exists()) {
                        props.load(Streams.utf8r(is = new FileInputStream(file)));
                    } else {
                        props.load(is = Files.findFileAsStream(plugin.config(), "utf-8"));
                    }
                } catch (Exception ex) {
                    throw new ServiceException(I18N.getLabel(
                            "easyplatform.plugins.config.error",
                            clazz.getName()), ex);
                } finally {
                    Streams.safeClose(is);
                }
            }
            try {
                Mirror<ApplicationService> mirror = (Mirror<ApplicationService>) Mirror.me(clazz);
                ApplicationService s = mirror.born(new ApplicationContext(this, props));
                s.start();
                services.put(s.getId(), s);
            } catch (ServiceException ex) {
                throw ex;
            } catch (Exception ex) {
                throw new ServiceException(I18N.getLabel(
                        "easyplatform.plugins.invalid", clazz.getName()), ex);
            }
        } else {
            Service service = clazz.getAnnotation(Service.class);
            if (service != null && service.type() == ServiceType.SERVER) {
                try {
                    EngineService es = (EngineService) clazz.newInstance();
                    es.setCommandExecutor(commandExecutor);
                    engineServices.put(service.value(), es);
                } catch (Exception ex) {
                    throw new ServiceException(I18N.getLabel(
                            "easyplatform.plugins.invalid", clazz.getName()), ex);
                }
            }//if
        }//else
    }

    @Override
    public TaskExecuter getTaskExecuter(CommandContext cc) {
        return taskExecuterFactory.create(cc);
    }

    private class Resource {

        String fullname;

        String name;

        InputStream is;

        Resource(String fullname, String name, InputStream is) {
            this.fullname = fullname;
            this.name = name;
            this.is = is;
        }
    }
}
